package mn.more.foundation.convert;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.StringTokenizer;
import mn.more.foundation.lang.StringUtil;
import org.apache.log4j.Logger;

/**
 * Utility-style conversion helper to translate to and from
 * <code>java.util.Date</code> objects.  The actual conversion is revealed in
 * the method parameters.<p>
 * <p/>
 * It is noteworthy to document that date formatting methods can be found in
 * <code>org.javaexpert.foundation.format.DateFormatter</code> class.
 *
 * @author <a href="mailto:mike.liu@aptechmongolia.edu.mn">Mike Liu</a>
 * @version $Id: DateConverter.java 5 2008-09-01 12:08:42Z mikeliucc $
 */
public final class DateConverter {

	private static final Logger LOG = Logger.getLogger(DateConverter.class);
	private static final DateFormat FULL_DATE_FORMAT =
		new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	private static final DateFormat SHORT_DATE_FORMAT =
		new SimpleDateFormat("yyyy-MM-dd");
	private static final DateFormat DEFAULT_DATE_FORMAT =
		DateFormat.getDateInstance();
	private static final DateFormat SHORT_DATE_FORMATTER =
		DateFormat.getDateInstance(DateFormat.SHORT);
	private static final String SHORT_DATE_DELIMITER = "/";
	private static final boolean ENFORCE_Y2K = true;

	/** singleton class, constr. is private */
	private DateConverter() { }

	/**
	 * convert <code>dateString</code> into a <code>java.util.Date</code> object,
	 * using <code>format</code> as a guide of the content of
	 * <code>dateString</code>.  Null means that <code>dateString</code> is not
	 * compatible with <code>format</code>.
	 *
	 * @return date object
	 */
	public static Date toDate(String dateString, String format) {
		if (StringUtil.isBlank(dateString)) { return null; }

		if (StringUtil.isBlank(format)) {
			// use default format
			return toDate(dateString);
		} else {
			try {
				return new SimpleDateFormat(format).parse(dateString);
			} catch (ParseException e) {
				System.err.println("unable to parse " + dateString +
				                   " as '" + format + "': " + e);
				return null;
			}
		}
	}

	/**
	 * convert <code>dateString</code> into a <code>java.util.Date</code> object
	 * using default format to contextualize the content of
	 * <code>dateString</code>.  Null means that <code>dateString</code> is not
	 * compatible with the default format.  The default format is determined by the
	 * system which this class runs under.
	 *
	 * @return date object
	 */
	public static Date toDate(String dateString) {
		if (StringUtil.isBlank(dateString)) { return null; }

		try {
			return DEFAULT_DATE_FORMAT.parse(dateString);
		} catch (ParseException e) {
			System.err.println("unable to parse " + dateString +
			                   " with default format: " + e);
			return null;
		}
	}

	/**
	 * return a <code>String</code> representation of the <code>date</code> object
	 * in the "Short Date" format.  The "Short Date" format might defered from
	 * systems to systems.  But in general, it should be include just the date
	 * (without time), like MM/DD/YYYY.
	 *
	 * @param date java.util.Date
	 * @return formatted date string
	 */
	public static String toShortDate(Date date) {
		// sanity check
		if (date == null) { return null; }

		// setup
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		String dateString = SHORT_DATE_FORMATTER.format(date);

		// Y2K check
		if (ENFORCE_Y2K) {
			StringTokenizer stkz =
				new StringTokenizer(dateString, SHORT_DATE_DELIMITER);
			if (stkz.countTokens() != 3) {
				// error?!?! what kind of date is this?
				System.err.println("invalid date format encountered: " +
				                   dateString);
				return null;
			}

			// day of the month
			String dayString = stkz.nextToken();
			if (dayString.length() == 1) { dayString = "0" + dayString; }

			// month
			String monthString = stkz.nextToken();
			if (monthString.length() == 1) { monthString = "0" + monthString; }

			// year
			String yearString = stkz.nextToken();
			if (yearString.length() < 4) {
				// not Y2K compliant month encountered
				// let's fix it with the 50/50 rule (following Oracle's solution)
				if (calendar.get(Calendar.YEAR) < 100) {
					if (calendar.get(Calendar.YEAR) >= 50) {
						// assuming 1950 and up
						yearString = String.valueOf(1900 + calendar
							.get(Calendar.YEAR));
					} else {
						// assuming 20xx
						yearString = String.valueOf(2000 + calendar
							.get(Calendar.YEAR));
					}
				} else {
					yearString = String.valueOf(calendar.get(Calendar.YEAR));
				}

				dateString = dayString +
				             SHORT_DATE_DELIMITER +
				             monthString +
				             SHORT_DATE_DELIMITER +
				             yearString;
			}
		}

		// done!
		return dateString;
	}

	public static String toFullDate(long timestamp) {
		return FULL_DATE_FORMAT.format(new Date(timestamp));
	}

	public static String nowFull() {
		return FULL_DATE_FORMAT.format(new Date());
	}

	public static Date toFullDate(String dateString) {
		try {
			return FULL_DATE_FORMAT.parse(dateString);
		} catch (ParseException e) {
			try {
				return SHORT_DATE_FORMAT.parse(dateString);
			} catch (ParseException e1) {
				LOG.fatal("Invalid date: " + dateString, e);
				return null;
			}
		}
	}

	public static Date addDay(Date date, int days) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		c.add(Calendar.DATE, days);
		return c.getTime();
	}
}
